package com.taoge.autoconfig;

import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis限流所需配置
 * @author Mr.Tao 
 */
@ConditionalOnExpression("#{'true'.equals(environment['rate-limit.enabled']) and ('2'.equals(environment['rate-limit.type']) or '3'.equals(environment['rate-limit.type']) or '-1'.equals(environment['rate-limit.type']))}")
@Configuration(value = "limitRedisAutoConfig")
public class RedisAutoConfig {

    @Bean(name = "stringLimitRedisSerializer")
    public StringRedisSerializer stringLimitRedisSerializer() {
        return new StringRedisSerializer();
    }

    @Bean(name = "genericJackson2JsonLimitRedisSerializer")
    public GenericJackson2JsonRedisSerializer genericJackson2JsonLimitRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }

    @Bean(name = "limitRedisTemplate")
    public RedisTemplate<String, Long> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Long> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(stringLimitRedisSerializer());
        template.setValueSerializer(genericJackson2JsonLimitRedisSerializer());
        template.setHashKeySerializer(stringLimitRedisSerializer());
        template.setHashValueSerializer(genericJackson2JsonLimitRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

    @Bean(name = "limitRedisLuaScript")
    public DefaultRedisScript<Long> redisLuaScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(limitScriptText());
        redisScript.setResultType(Long.class);
        return redisScript;
    }

    private String limitScriptText() {
        return """
                    -- 拿到Redis中的key KEYS[1]:获取传递进来的key中的第一个key
                    local key = "rate-limit:" .. KEYS[1]
                    -- ARGV[1]:获取传递进来的可变参数中的第一个参数 tonumber:尝试将它的参数转换为数字
                    -- limit:单位时间内的限制次数
                    local limit = tonumber(ARGV[1])
                    local time = tonumber(ARGV[2])
                    -- redis.call():在lua中执行Redis命令,
                    -- 获取当前key的使用次数
                    local current = tonumber(redis.call('get', key) or "0")
                    -- 如果 使用次数加一大于限制的次数,则说明达到了阈值,进行限流
                    if current + 1 > limit then
                      return 0
                    else
                      -- 将key中储存的数字加上指定的增量值,如果key不存在,那么key的值会先被初始化为0,然后再执行INCRBY命令
                      redis.call("INCRBY", key,"1")
                      -- 设置key的过期时间
                      redis.call("expire", key, time)
                      return current + 1
                    end
               """;
    }

}
